﻿using System;
using System.Globalization;
using System.Runtime.InteropServices;

namespace Microsoft.Xna.Framework
{
    /// <summary>
    /// 	Defines a ray.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Rayline : IEquatable<Rayline>
    {
        #region Static Operations

        #region Equality & Inequality

        /// <summary>
        /// 	Returns a value that indicates whether two specified Ray2D values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(Rayline left, Rayline right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified Ray2D values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(Rayline left, Rayline right)
        {
            if (left.Position == right.Position)
            {
                return left.Direction != right.Direction;
            }
            return true;
        }

        #endregion

        #endregion

        #region Fields & Properties

        /// <summary>
        /// 	Specifies the starting point of the Ray2D.
        /// </summary>
        public Vector2 Position;

        /// <summary>
        /// 	Unit vector specifying the direction the Ray2D is pointing.
        /// </summary>
        public Vector2 Direction;

        #endregion

        #region Constructors

        /// <summary>
        /// 	Creates a new instance of Ray2D.
        /// </summary>
        /// <param name="position"> The starting point of the Ray2D. </param>
        /// <param name="direction"> Unit vector describing the direction of the Ray2D. </param>
        public Rayline(Vector2 position, Vector2 direction)
        {
            Position = position;
            Direction = direction;
        }

        #endregion

        #region Implementation of IEquatable<Ray2D>

        /// <summary>
        /// 	Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns> true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false. </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public bool Equals(Rayline other)
        {
            return Position == other.Position && Direction == other.Direction;
        }

        #endregion

        #region Overrides of ValueType

        /// <summary>
        /// 	Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns> true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
        /// <param name="obj"> Another object to compare to. </param>
        public override bool Equals(object obj)
        {
            var result = false;
            if (obj != null && obj is Rayline)
            {
                result = Equals((Rayline) obj);
            }
            return result;
        }

        /// <summary>
        /// 	Returns the hash code for this instance.
        /// </summary>
        /// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
        public override int GetHashCode()
        {
            return Position.GetHashCode() + Direction.GetHashCode();
        }

        /// <summary>
        /// 	Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "{{Position:{0} Direction:{1}}}", Position, Direction);
        }

        #endregion

        #region Intersects

        /// <summary>
        /// 	Checks whether the Ray2D intersects a specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with the Ray2D. </param>
        public float? Intersects(BoundingRectangle rectangle)
        {
            return rectangle.Intersects(this);
        }

        /// <summary>
        /// 	Checks whether the current Ray2D intersects a BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] Distance at which the ray intersects the BoundingRectangle or null if there is no intersection. </param>
        public void Intersects(ref BoundingRectangle rectangle, out float? result)
        {
            rectangle.Intersects(ref this, out result);
        }

        /// <summary>
        /// 	Checks whether the Ray2D intersects a specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with the Ray2D. </param>
        public float? Intersects(BoundingCircle circle)
        {
            var distanceX = circle.Center.X - Position.X;
            var distanceY = circle.Center.Y - Position.Y;
            var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
            var radiusSquared = circle.Radius * circle.Radius;
            if (distanceSquared <= radiusSquared)
            {
                return 0f;
            }
            var directionalDistance = (distanceX * Direction.X) + (distanceY * Direction.Y);
            if (directionalDistance < 0f)
            {
                return null;
            }
            var finalSquared = distanceSquared - (directionalDistance * directionalDistance);
            if (finalSquared > radiusSquared)
            {
                return null;
            }
            var distance = (float) Math.Sqrt(radiusSquared - finalSquared);
            return directionalDistance - distance;
        }

        /// <summary>
        /// 	Checks whether the current Ray2D intersects a BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] Distance at which the ray intersects the BoundingCircle or null if there is no intersection. </param>
        public void Intersects(ref BoundingCircle circle, out float? result)
        {
            var distanceX = circle.Center.X - Position.X;
            var distanceY = circle.Center.Y - Position.Y;
            var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
            var radiusSquared = circle.Radius * circle.Radius;
            if (distanceSquared <= radiusSquared)
            {
                result = 0f;
            }
            else
            {
                result = 0;
                var directionalDistance = (distanceX * Direction.X) + (distanceY * Direction.Y);
                if (directionalDistance >= 0f)
                {
                    var finalSquared = distanceSquared - (directionalDistance * directionalDistance);
                    if (finalSquared <= radiusSquared)
                    {
                        var distance = (float) Math.Sqrt(radiusSquared - finalSquared);
                        result = directionalDistance - distance;
                    }
                }
            }
        }

        #endregion
    }
}